#include "s3e.h"
#include "IwUtil.h"
#include "Iw2D.h"
#include "IwResManager.h"
#include "stdio.h"
#include <ctime>
#include <list>
#include <vector>
#include "ui_menu.h"
#include "ui_page.h"
#include "ui_widgets.h"
#include "block.h"
#include "grid.h"
#include "swipe.h"
#include "game.h"
#include "buttonfunctions.h"
#include "IwSound.h"
#include "IwRandom.h"
#include "util.h"
#include "engine.h"

//#define IW_MANAGED_DECLARE
//#define IW_MANAGED_IMPLEMENT

Engine* Engine::m_Singleton = NULL;	// Global string buffer used to avoid having to allocate memory each time a c-string is needed somewhere. 
									// (Which inevitable leads to memory leaks in Marmalade, as we've found)

// These strings are used to print the internal states textually (as opposed to having only integers to represent a state).
const char* EngineStateStrings[3] = {	"MAINMENU", 
										"GAME",
										"EXIT"	};
const char* GameStateStrings[4] = {	"NORMAL", 
									"LINEANIMATING",
									"GAMEOVER",
									"PAUSED"	};

char g_StringBuffer[256];

void Engine::Run()
{
	// Initialize the engine.
	Init();

	// Start the main engine loop.
	Loop();

	// Terminate the engine.
	Terminate();
}

void Engine::Init()
{
	// Initialize stuff for the engine.
	IwUtilInit();	// Initialize the Util lib.
	Iw2DInit();		// Initialize the 2D lib.
	IwResManagerInit();
	IwSoundInit();

	// Seed random.
	IwRandSeed(int32(s3eTimerGetUTC()));

	// Load the resource group.
	IwGetResManager()->AddHandler(new CIwResHandlerWAV());
	IwGetResManager()->LoadGroup("Resources.group");

	// Prepare the IwGxFont resource for rendering using Iw2D
	m_Font = Iw2DCreateFontResource("font_telemarines");
		//(CIw2DFont*)IwGetResManager()->GetResNamed("font_telemarines", "CIwGxFont");

	m_Background = Iw2DCreateImageResource("background_and_logo");

	m_LoadingImage = Iw2DCreateImageResource("loading");

	m_Menu = new UI::Menu();
	m_Game = new Game();
	m_MusicSpec = (CIwSoundSpec*)IwGetResManager()->GetResNamed("tetris_theme", IW_SOUND_RESTYPE_SPEC);
	
	// Initialize member variables.
	m_MaxMusicVolume = 4096.0f;
	m_MinMusicVolume = 0.0f;
	m_MusicVolume = (m_MaxMusicVolume - m_MinMusicVolume) / 2.0f;
	m_CurrentHighscore = 0;

	int BlockDimensionsOnScreen = int( TO_SCREENX(37.0f) );
	m_BackgroundBlocksSize.x = BlockDimensionsOnScreen;
	m_BackgroundBlocksSize.y = BlockDimensionsOnScreen;
	int NumBlocksInWidth = Iw2DGetSurfaceWidth() / BlockDimensionsOnScreen;
	int NumBlocksInHeight = Iw2DGetSurfaceHeight() / BlockDimensionsOnScreen;
	for (int i = 0; i < NUM_BACKGROUND_BLOCKS; ++i)
	{
		m_BackgroundBlocksPositions[i] = CIwSVec2(	(Rand()%NumBlocksInWidth) * BlockDimensionsOnScreen, 
													-(BlockDimensionsOnScreen * (Rand()%(NumBlocksInHeight))));
		m_BackgroundBlocksTypes[i] = Rand()%(Block::MAX + 1);
	}

	// Construct the main menu.
	ConstructMainMenu();
}

void Engine::Terminate()
{
	if (m_Game)
		delete m_Game;
	m_Game = NULL;

	if (m_Font)
		delete m_Font;
	m_Font = NULL;

	if (m_Menu)
	{
		m_Menu->Delete();
		delete m_Menu;
	}
	m_Menu = NULL;

	if (m_Background)
		delete m_Background;
	m_Background = NULL;

	// Does not need to be deleted, is handled by the sound engine.
	// Deleting this manually may even cause a crash on exit.
// 	if (m_MusicInstance)
// 		delete m_MusicInstance;
 	m_MusicInstance = NULL;
	
	if (m_MusicSpec)
		delete m_MusicSpec;
	m_MusicSpec = NULL;

	if (m_LoadingImage)
		delete m_LoadingImage;
	m_LoadingImage = NULL;

	IwGetResManager()->DestroyGroup("Resources");

	IwSoundTerminate();
	IwResManagerTerminate();
	Iw2DTerminate();	// Release the 2D Lib.
	IwUtilTerminate();	// Release the Util Library.
}

void Engine::Loop()
{
	uint16 deltaMs = 0;
	uint64 currTime, lastTime = s3eTimerGetMs();

	// Start playing music.
	m_MusicInstance = m_MusicSpec->Play();
	UpdateMusicVolume();

	while (m_EngineState != Engine::EXIT)
	{
		// Update input.
		s3eKeyboardUpdate();
		s3ePointerUpdate();

		// Calculate delta time.
		currTime = s3eTimerGetMs();
		deltaMs = (uint16)(currTime - lastTime);
		lastTime = currTime;
		if (deltaMs > 200) deltaMs = 200;

		// Clear the surface.
		Iw2DSurfaceClear(0);

		// Render the background, this is always visible.
		Iw2DSetColour(0xffffffff);
		Iw2DDrawImage(m_Background, CIwSVec2(0, 0), CIwSVec2(Iw2DGetSurfaceWidth(), Iw2DGetSurfaceHeight()));

		// Update sound.
		IwGetSoundManager()->Update();

		// Perform logic and render calls.
		switch (m_EngineState)
		{
		case Engine::MAINMENU:
			{
				// Update the random background blocks.
				for (int i = 0; i < NUM_BACKGROUND_BLOCKS; ++i)
				{
					m_BackgroundBlocksPositions[i].y += int(SPEED_BACKGROUND_BLOCKS * deltaMs);

					if (m_BackgroundBlocksPositions[i].y > Iw2DGetSurfaceHeight())
					{
						m_BackgroundBlocksPositions[i].y = -((m_BackgroundBlocksSize.y + 1) * (Rand()%((Iw2DGetSurfaceHeight() / m_BackgroundBlocksSize.y))));
						m_BackgroundBlocksPositions[i].x = (Rand()%(Iw2DGetSurfaceWidth() / m_BackgroundBlocksSize.x)) * (m_BackgroundBlocksSize.x + 1);
						m_BackgroundBlocksTypes[i] = Rand()%(Block::MAX + 1);
					}
				}

				// Render the random background blocks.
				Iw2DSetColour(0xffffffff);
				for (int i = 0; i < NUM_BACKGROUND_BLOCKS; ++i)
				{
					Iw2DDrawImage(m_Game->GetGrid()->m_BlockTypeImages[m_BackgroundBlocksTypes[i]], m_BackgroundBlocksPositions[i], m_BackgroundBlocksSize);
				}
				Iw2DSetColour(0xffffffff);


				m_Menu->Update(deltaMs);
				m_Menu->Render();

				sprintf(g_StringBuffer, "Highscore: %i", m_CurrentHighscore);
				Iw2DDrawString(g_StringBuffer, CIwSVec2(0, 0), CIwSVec2( int16(Iw2DGetSurfaceWidth() * 0.985f), int16(Iw2DGetSurfaceHeight() * 0.985f)), IW_2D_FONT_ALIGN_CENTRE, IW_2D_FONT_ALIGN_BOTTOM);
				break;
			}
		case Engine::GAME:
			{
				m_Game->Update( deltaMs);
				break;
			}
		}

// #ifdef _DEBUG
// 		DrawDebugInfo();
// #endif

		// Show the surface.
		Iw2DSurfaceShow();

		// Yield the CPI, allow other processes to use the CPU.
		s3eDeviceYield(10);

		// DEBUG PURPOSES:
		if (s3eDeviceCheckQuitRequest()) SwitchEngineState(Engine::EXIT);
	}

	// Stop playing the music.
	m_MusicInstance->Stop();
}

void Engine::DrawDebugInfo()
{
	sprintf(g_StringBuffer, "Engine State: %s\t%s\nGame State: %s", 
			EngineStateStrings[m_EngineState], 
			((m_EngineState == Engine::MAINMENU)?(m_Menu->GetCurrentPageName()):("")), 
			GameStateStrings[m_Game->GetGameState()]);	

	Iw2DSetColour(0xffffffff);
	Iw2DDrawString(g_StringBuffer, CIwSVec2(2, 2), CIwSVec2( Iw2DGetSurfaceWidth(), Iw2DGetSurfaceHeight() ), IW_2D_FONT_ALIGN_LEFT, IW_2D_FONT_ALIGN_TOP);
}

void Engine::ConstructMainMenu()
{
	// --------- General page.
	UI::Page* page = new UI::Page( "MainMenu_General" );

	UI::Button* button = new UI::Button( CIwFVec2(0.10f, 0.335f), CIwFVec2(0.78f, 0.12f), "btn_menu_play", "btn_menu_play_down", NULL, &btnFunction_GoToGame );
	page->AddWidget(button);

	button = new UI::Button( CIwFVec2(0.17f, 0.532f), CIwFVec2(0.65f, 0.1f), "btn_menu_options", "btn_menu_options_down", NULL, &btnFunction_GoToOptions );
	page->AddWidget(button);

	button = new UI::Button( CIwFVec2(0.17f, 0.667f), CIwFVec2(0.65f, 0.1f), "btn_menu_credits", "btn_menu_credits_down", NULL, &btnFunction_GoToCredits );
	page->AddWidget(button);

	button = new UI::Button( CIwFVec2(0.27f, 0.817f), CIwFVec2(0.45f, 0.06923f), "btn_game_quit", "btn_game_quit_down", NULL, &btnFunction_EndApplication );
	page->AddWidget(button);

	m_Menu->AddPage(page);


	// --------- Options page.	
	page = new UI::Page( "MainMenu_Options" );

	button = new UI::Button( CIwFVec2(0.10f, 0.7f), CIwFVec2(0.78f, 0.12f), "btn_menu_resetscores", "btn_menu_resetscores_down", NULL, &btnFunction_ResetScores );
	page->AddWidget(button);

	button = new UI::Button( CIwFVec2(0.1f, 0.85f), CIwFVec2(0.35f, 0.1f), "btn_menu_apply", "btn_menu_apply_down", NULL, &btnFunction_ApplyOptions );
	page->AddWidget(button);

	button = new UI::Button( CIwFVec2(0.55f, 0.85f), CIwFVec2(0.35f, 0.1f), "btn_menu_back", "btn_menu_back_down", NULL, &btnFunction_GoToGeneral );
	page->AddWidget(button);

	UI::Image* image = new UI::Image( CIwFVec2(0.125f, 0.282f), CIwFVec2(0.75f, 0.04f), "img_slider_title_sound" );
	page->AddWidget(image);

	UI::Slider* slider = new UI::Slider( CIwFVec2(0.125f, 0.332f), CIwFVec2(0.75f, 0.1f), "slider", m_MusicVolume, m_MinMusicVolume, m_MaxMusicVolume, NULL, NULL );
	page->AddWidget(slider);

// 	image = new UI::Image( CIwFVec2(0.125f, 0.482f), CIwFVec2(0.75f, 0.04f), "img_slider_title_gamespeed" );
// 	page->AddWidget(image);
// 
// 	slider = new UI::Slider( CIwFVec2(0.125f, 0.532f), CIwFVec2(0.75f, 0.1f), "slider", m_Game->GetGrid()->m_BlockMoveInterval, GAME_SPEED_MIN, GAME_SPEED_MAX, true, NULL, NULL );
// 	page->AddWidget(slider);

	m_Menu->AddPage(page);


	// --------- Credits page.
	page = new UI::Page( "MainMenu_Credits" );
	
	image = new UI::Image( CIwFVec2(0.0315f, 0.182f), CIwFVec2(0.937f, 0.7f), "img_credits_page" );
	page->AddWidget(image);

	button = new UI::Button( CIwFVec2(0.55f, 0.85f), CIwFVec2(0.35f, 0.1f), "btn_menu_back", "btn_menu_back_down", NULL, &btnFunction_GoToGeneral );
	page->AddWidget(button);

	m_Menu->AddPage(page);
}

Engine::T_ENGINESTATE Engine::GetEngineState()
{
	return m_EngineState;
}

void Engine::SwitchEngineState( Engine::T_ENGINESTATE a_State )
{
	switch (a_State)
	{
	case Engine::MAINMENU:
		{
			break;
		}
	case Engine::GAME:
		{
			// Display loading screen.
			Iw2DSurfaceClear(0x00000000);
			Iw2DDrawImage(m_LoadingImage, CIwSVec2(0, 0), CIwSVec2(Iw2DGetSurfaceWidth(), Iw2DGetSurfaceHeight()));
			Iw2DSurfaceShow();

			// Reset game.
			if (m_Game)
				delete m_Game;
			m_Game = new Game();

			break;
		}
	case Engine::EXIT:
		{
			break;
		}
	}

	m_EngineState = a_State;
}

void Engine::UpdateMusicVolume()
{
	m_MusicInstance->SetVol(iwsfixed(m_MusicVolume));
}

int Engine::GetCurrentHighscore()
{
	return m_CurrentHighscore;
}

void Engine::SetHighscore( int a_Score )
{
	m_CurrentHighscore = a_Score;
}

Engine::Engine()
	: m_EngineState( Engine::MAINMENU )
	, m_Game( NULL )
	, m_Menu( NULL )
{
	SetSingletonPtr(this);
}

Engine::~Engine()
{

}